CCCPaste

202edd135b801

/**
 * Revelor Search Bundle
 * Combined and bundled JavaScript for Shoptet
 * 
 * Contains:
 * - Console Wrapper (admin-only logging)
 * - DataLayer Helper (GTM analytics)
 * - Search Tracker (backend tracking)
 * - Search Overlay (main search functionality)
 * - Search Results Page (results page logic)
 * 
 * Build date: 2025-11-01T00:12:21.753Z
 * Project: Revelor Textilomanie
 */

// =============================================================================
// console-wrapper.js
// =============================================================================

/**
 * @fileoverview Console Wrapper for Admin-Only Logging
 * Přepisuje console metody - výstupy se zobrazí pouze pokud je admin přihlášený
 * @version 1.0.0
 * @author Revelor by Webotvůrci
 */

(function() {
  'use strict';

  /**
   * Zkontroluje, jestli je admin přihlášený
   * Kontroluje dynamicky při každém volání (BODY může být ještě ne načtený)
   * @returns {boolean}
   */
  function isAdminLoggedIn() {
    // Pokud BODY ještě není načtené, vrať false (bezpečnější)
    if (!document.body) {
      return false;
    }
    // Zkontroluj, jestli má BODY class 'admin-logged'
    return document.body.classList.contains('admin-logged');
  }

  /**
   * Uložit původní console metody
   */
  const originalConsole = {
    log: console.log.bind(console),
    error: console.error.bind(console),
    warn: console.warn.bind(console),
    info: console.info.bind(console),
    debug: console.debug.bind(console),
    trace: console.trace.bind(console),
    table: console.table.bind(console),
    group: console.group.bind(console),
    groupEnd: console.groupEnd.bind(console),
    groupCollapsed: console.groupCollapsed.bind(console),
    time: console.time.bind(console),
    timeEnd: console.timeEnd.bind(console),
    assert: console.assert.bind(console)
  };

  /**
   * No-op funkce (nic nedělá)
   */
  function noop() {}

  /**
   * Wrapper funkce, která zkontroluje admin status
   */
  function createConsoleWrapper(originalMethod) {
    return function(...args) {
      if (isAdminLoggedIn()) {
        originalMethod.apply(console, args);
      }
      // Pokud není admin, nic se nestane (no-op)
    };
  }

  // Přepiš console metody pouze pokud ještě nejsou přepsané
  if (!window._revelorConsoleWrapped) {
    console.log = createConsoleWrapper(originalConsole.log);
    console.error = createConsoleWrapper(originalConsole.error);
    console.warn = createConsoleWrapper(originalConsole.warn);
    console.info = createConsoleWrapper(originalConsole.info);
    console.debug = createConsoleWrapper(originalConsole.debug);
    console.trace = createConsoleWrapper(originalConsole.trace);
    console.table = createConsoleWrapper(originalConsole.table);
    console.group = createConsoleWrapper(originalConsole.group);
    console.groupEnd = createConsoleWrapper(originalConsole.groupEnd);
    console.groupCollapsed = createConsoleWrapper(originalConsole.groupCollapsed);
    console.time = createConsoleWrapper(originalConsole.time);
    console.timeEnd = createConsoleWrapper(originalConsole.timeEnd);
    console.assert = createConsoleWrapper(originalConsole.assert);

    // Označit, že jsme už wrapper aplikovali
    window._revelorConsoleWrapped = true;
  }
})();




// =============================================================================
// datalayer-helper.js
// =============================================================================

/**
 * @fileoverview DataLayer Helper for Revelor Search Analytics
 * Manages all dataLayer pushes for Google Tag Manager
 * @version 2.0.0
 * @author Revelor by Webotvůrci
 * @contact [email protected] | +420 728 089 029
 */

class RevelorDataLayer {
  constructor() {
    // Initialize dataLayer if not exists
    window.dataLayer = window.dataLayer || [];
    
    // Session storage for tracking search journey
    this.sessionKey = 'revelor_search_session';
    this.initSession();
  }

  /**
   * Initialize search session
   */
  initSession() {
    if (!sessionStorage.getItem(this.sessionKey)) {
      sessionStorage.setItem(this.sessionKey, JSON.stringify({
        searchCount: 0,
        lastSearchTerm: null,
        lastSearchTime: null,
        searchStartTime: null
      }));
    }
  }

  /**
   * Get search session data
   */
  getSession() {
    return JSON.parse(sessionStorage.getItem(this.sessionKey) || '{}');
  }

  /**
   * Update search session
   */
  updateSession(updates) {
    const session = this.getSession();
    const newSession = { ...session, ...updates };
    sessionStorage.setItem(this.sessionKey, JSON.stringify(newSession));
  }

  /**
   * Get current timestamp in ISO format
   */
  getTimestamp() {
    return new Date().toISOString();
  }

  /**
   * Get user type (new vs returning)
   */
  getUserType() {
    // Check if user has visited before (using cookie or localStorage)
    return localStorage.getItem('user_visited') ? 'returning' : 'new';
  }

  /**
   * Get page type from URL or body class
   */
  getPageType() {
    const path = window.location.pathname;
    if (path === '/' || path === '') return 'homepage';
    if (path.includes('/produkt/') || path.includes('/product/')) return 'product';
    if (path.includes('/kategorie/') || path.includes('/category/')) return 'category';
    if (path.includes('/vysledky-hledani') || path.includes('/search')) return 'search_results';
    return 'other';
  }

  /**
   * Calculate time difference in seconds
   */
  getTimeDiff(startTime) {
    if (!startTime) return 0;
    return ((Date.now() - new Date(startTime).getTime()) / 1000).toFixed(1);
  }

  /**
   * Push event to dataLayer
   */
  push(eventData) {
    console.log('📊 Revelor DataLayer Push:', eventData);
    window.dataLayer.push(eventData);
  }

  /**
   * 1. Search Started
   */
  searchStarted(context = 'overlay') {
    const session = this.getSession();
    
    this.push({
      event: 'revelor_search_started',
      search_context: context,
      timestamp: this.getTimestamp(),
      user_type: this.getUserType(),
      page_type: this.getPageType(),
      session_search_count: session.searchCount || 0
    });

    // Update session
    this.updateSession({
      searchStartTime: this.getTimestamp(),
      searchCount: (session.searchCount || 0) + 1
    });
  }

  /**
   * 2. Search Query Submitted
   */
  searchQuerySubmitted(searchTerm, context = 'overlay') {
    const session = this.getSession();
    const isRefinement = session.lastSearchTerm && session.lastSearchTerm !== searchTerm;

    this.push({
      event: 'revelor_search_query_submitted',
      search_term: searchTerm,
      search_term_length: searchTerm.length,
      search_context: context,
      is_refinement: isRefinement,
      previous_search_term: isRefinement ? session.lastSearchTerm : null,
      timestamp: this.getTimestamp()
    });

    // Update session
    this.updateSession({
      lastSearchTerm: searchTerm,
      lastSearchTime: this.getTimestamp()
    });
  }

  /**
   * 3. Search Results Displayed
   */
  searchResultsDisplayed(searchTerm, resultsData, responseTime, page = 1) {
    const totalResults = resultsData.total_products || 0;
    const productsCount = resultsData.products?.length || 0;
    const categoriesCount = resultsData.categories?.length || 0;
    const articlesCount = resultsData.articles?.length || 0;
    const brandsCount = resultsData.brands?.length || 0;

    this.push({
      event: 'revelor_search_results_displayed',
      search_term: searchTerm,
      results_count: {
        total: totalResults,
        products: productsCount,
        categories: categoriesCount,
        articles: articlesCount,
        brands: brandsCount
      },
      has_results: totalResults > 0,
      page: page,
      response_time_ms: responseTime,
      timestamp: this.getTimestamp()
    });
  }

  /**
   * 4. Search No Results
   */
  searchNoResults(searchTerm) {
    // Simple typo detection (contains numbers mixed with letters, etc.)
    const potentialTypo = /\d/.test(searchTerm) || searchTerm.length < 3;

    this.push({
      event: 'revelor_search_no_results',
      search_term: searchTerm,
      search_term_length: searchTerm.length,
      potential_typo: potentialTypo,
      timestamp: this.getTimestamp()
    });
  }

  /**
   * 5. Search Result Click
   */
  searchResultClick(searchTerm, resultType, resultData, position, totalResults) {
    const session = this.getSession();
    const clickTimeFromSearch = this.getTimeDiff(session.lastSearchTime);

    const eventData = {
      event: 'revelor_search_result_click',
      search_term: searchTerm,
      result_type: resultType,
      result_position: position,
      total_results: totalResults,
      click_time_from_search: parseFloat(clickTimeFromSearch),
      timestamp: this.getTimestamp()
    };

    // Add result-specific data
    if (resultType === 'product') {
      eventData.result_data = {
        item_id: resultData._id || resultData.id,
        item_name: resultData.title,
        item_category: resultData.category || 'Neznámá kategorie',
        price: resultData.price_s_dph || 0,
        currency: 'CZK',
        availability: resultData.skladem ? 'in_stock' : 'out_of_stock',
        item_url: resultData.url
      };

      // GA4 Enhanced Ecommerce format
      eventData.ecommerce = {
        currency: 'CZK',
        value: resultData.price_s_dph || 0,
        items: [{
          item_id: resultData._id || resultData.id,
          item_name: resultData.title,
          item_list_name: 'Search Results',
          item_list_id: 'search_results',
          price: resultData.price_s_dph || 0,
          index: position,
          quantity: 1
        }]
      };
    } else if (resultType === 'category') {
      eventData.result_data = {
        category_name: resultData.title,
        category_url: resultData.url
      };
    } else if (resultType === 'article') {
      eventData.result_data = {
        article_title: resultData.title,
        article_url: resultData.url
      };
    }

    this.push(eventData);
  }

  /**
   * 6. Search Load More
   */
  searchLoadMore(searchTerm, currentPage, totalPages, productsLoaded, totalProductsShown) {
    this.push({
      event: 'revelor_search_load_more',
      search_term: searchTerm,
      current_page: currentPage,
      next_page: currentPage + 1,
      total_pages: totalPages,
      products_loaded: productsLoaded,
      total_products_shown: totalProductsShown,
      timestamp: this.getTimestamp()
    });
  }

  /**
   * 7. Search Refinement
   */
  searchRefinement(originalTerm, refinedTerm, originalResults, refinedResults) {
    // Determine refinement type
    let refinementType = 'modification';
    if (refinedTerm.length > originalTerm.length) {
      refinementType = 'expansion';
    } else if (refinedTerm.length < originalTerm.length) {
      refinementType = 'narrowing';
    } else if (Math.abs(refinedTerm.length - originalTerm.length) <= 2) {
      refinementType = 'correction';
    }

    this.push({
      event: 'revelor_search_refinement',
      original_search_term: originalTerm,
      refined_search_term: refinedTerm,
      refinement_type: refinementType,
      original_results_count: originalResults,
      refined_results_count: refinedResults,
      timestamp: this.getTimestamp()
    });
  }

  /**
   * 8. Search Suggestion Click
   */
  searchSuggestionClick(typedQuery, suggestedTerm, position, suggestionType) {
    this.push({
      event: 'revelor_search_suggestion_click',
      typed_query: typedQuery,
      suggested_term: suggestedTerm,
      suggestion_position: position,
      suggestion_type: suggestionType,
      timestamp: this.getTimestamp()
    });
  }

  /**
   * 9. Search Recommendations Shown
   */
  searchRecommendationsShown(recommendationsData) {
    const products = recommendationsData.products || [];
    const categories = recommendationsData.categories || [];
    
    this.push({
      event: 'revelor_search_recommendations_shown',
      recommendations_count: products.length + categories.length,
      products_count: products.length,
      categories_count: categories.length,
      recommendation_type: 'default', // trending/popular/personalized
      timestamp: this.getTimestamp()
    });
  }

  /**
   * 10. Search Category Expand
   */
  searchCategoryExpand(searchTerm, expandType, action, visibleBefore, visibleAfter) {
    this.push({
      event: 'revelor_search_category_expand',
      search_term: searchTerm,
      expand_type: expandType, // 'categories' | 'articles'
      action: action, // 'expand' | 'collapse'
      visible_items_before: visibleBefore,
      visible_items_after: visibleAfter,
      timestamp: this.getTimestamp()
    });
  }

  /**
   * 11. Search Abandoned
   */
  searchAbandoned(searchTerm, resultsCount, timeSpent, reason = 'closed_overlay') {
    this.push({
      event: 'revelor_search_abandoned',
      search_term: searchTerm,
      results_count: resultsCount,
      time_spent: parseFloat(timeSpent),
      reason: reason, // 'closed_overlay' | 'navigated_away' | 'no_interaction'
      timestamp: this.getTimestamp()
    });
  }
}

// Initialize global instance
window.revelorDataLayer = new RevelorDataLayer();




// =============================================================================
// search-tracker.js
// =============================================================================

/**
 * @fileoverview Search Tracker for Backend Analytics
 * Tracks search events and clicks to backend API according to metrics-tracking.md
 * @version 1.0.0
 * @author Revelor by Webotvůrci
 * @contact [email protected] | +420 728 089 029
 */

class SearchTracker {
  constructor(config = {}) {
    this.apiEndpoint = config.apiEndpoint || 'https://textilomanie.revelor.cz/api/search/track';
    this.projectId = config.projectId || '312030';
    this.sessionTimeout = config.sessionTimeout || 1800000; // 30 minut v ms
    this.sessionKey = 'search_session_id';
    this.lastSearchTimeKey = 'search_last_time';
    
    // Generuj session ID pokud neexistuje nebo vypršela
    this.getOrCreateSession();
  }

  /**
   * Get or create session ID
   */
  getOrCreateSession() {
    const sessionId = sessionStorage.getItem(this.sessionKey);
    const lastSearchTime = sessionStorage.getItem(this.lastSearchTimeKey);
    const now = Date.now();
    
    // Pokud session neexistuje nebo vypršela (30 min), vytvoř novou
    if (!sessionId || !lastSearchTime || (now - parseInt(lastSearchTime)) > this.sessionTimeout) {
      const newSessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
      sessionStorage.setItem(this.sessionKey, newSessionId);
      sessionStorage.setItem(this.lastSearchTimeKey, now.toString());
      return newSessionId;
    }
    
    return sessionId;
  }

  /**
   * Get current session ID
   */
  getSessionId() {
    return this.getOrCreateSession();
  }

  /**
   * Update last search time
   */
  updateLastSearchTime() {
    sessionStorage.setItem(this.lastSearchTimeKey, Date.now().toString());
  }

  /**
   * Track search event
   * @param {Object} data - Search tracking data
   * @param {string} data.query - Search query (min 3 chars)
   * @param {number} [data.results_count] - Number of results
   * @param {string} [data.search_type] - 'manual' | 'suggestion' | 'live_search'
   */
  async trackSearch(data) {
    const { query, results_count, search_type = 'live_search' } = data;
    
    // Backend požaduje minimálně 3 znaky
    if (!query || query.length < 3) {
      console.log('🔇 Search tracking skipped - query too short:', query);
      return;
    }

    const sessionId = this.getSessionId();
    this.updateLastSearchTime();

    const eventData = {
      event_type: 'search',
      query: query,
      session_id: sessionId,
      url: window.location.href,
      referrer: document.referrer || '',
      timestamp: new Date().toISOString(),
      user_agent: navigator.userAgent,
      search_type: search_type
    };

    if (results_count !== undefined) {
      eventData.results_count = results_count;
    }

    await this.sendEvent(eventData);
  }

  /**
   * Track click event
   * @param {Object} data - Click tracking data
   * @param {string} data.clicked_result - Product/article/category ID (required)
   * @param {string} data.result_type - 'product' | 'article' | 'category'
   * @param {string} data.result_title - Title of the result
   * @param {string} data.result_url - URL of the result
   * @param {number} data.result_position - Position in results (1-based)
   * @param {string} data.query - Search query
   */
  async trackClick(data) {
    const { clicked_result, product_id, result_type, result_title, result_url, result_position, query } = data;
    
    // Backend vyžaduje clicked_result nebo product_id
    const resultId = clicked_result || product_id;
    if (!resultId) {
      console.error('❌ Click tracking error - missing clicked_result or product_id');
      return;
    }

    const sessionId = this.getSessionId();

    const eventData = {
      event_type: 'click',
      clicked_result: resultId,
      result_type: result_type || 'product',
      query: query || '',
      session_id: sessionId,
      url: window.location.href,
      referrer: document.referrer || '',
      timestamp: new Date().toISOString(),
      user_agent: navigator.userAgent
    };

    if (result_title) {
      eventData.result_title = result_title;
    }

    if (result_url) {
      eventData.result_url = result_url;
    }

    if (result_position !== undefined) {
      eventData.result_position = result_position;
    }

    // Alternativní název pro product_id (pro kompatibilitu)
    if (product_id) {
      eventData.product_id = product_id;
    }

    await this.sendEvent(eventData);
  }

  /**
   * Send event to backend API
   * @param {Object} eventData - Event data to send
   */
  async sendEvent(eventData) {
    try {
      const response = await fetch(this.apiEndpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Tenant': this.projectId
        },
        body: JSON.stringify(eventData)
      });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        console.error('❌ Search tracking error:', response.status, errorData);
        return;
      }

      const responseData = await response.json().catch(() => ({}));
      
      if (responseData.status === 'skipped') {
        console.log('⏭️ Search tracking skipped:', responseData.message);
      } else {
        console.log('✅ Search tracking success:', responseData);
      }
    } catch (error) {
      // Fail silently - tracking should not break the app
      console.warn('⚠️ Search tracking failed:', error);
    }
  }
}

// Create global instance
window.searchTracker = new SearchTracker({
  projectId: (typeof dataLayer !== 'undefined' && 
             dataLayer[0] && 
             dataLayer[0].shoptet && 
             dataLayer[0].shoptet.projectId) || '312030'
});




// =============================================================================
// searching.js
// =============================================================================

/**
 * @fileoverview Search Overlay Component
 * Implements search functionality using Shoptet API
 * @version 2.0.0
 * @author Revelor by Webotvůrci
 * @contact [email protected] | +420 728 089 029
 */

class SearchOverlay {
  constructor() {
    this.isOpen = false;
    this.apiBaseUrl = 'https://textilomanie.revelor.cz/api/public';
    this.projectId = this.getProjectId();
    this.minSearchLength = 1;
    this.previousActiveElement = null; // Store previous focus
    this.focusableElements = []; // Focusable elements in overlay
    this.currentFocusIndex = -1; // Current focus index for keyboard navigation
    
    // Performance optimizations
    this.abortController = null; // Request cancellation
    this.cache = new Map(); // Simple cache
    this.cacheTTL = 300000; // 5 minutes
    
    // Search history
    this.historyKey = 'revelor_search_history';
    this.maxHistoryItems = 10;

    this.init();
  }

  /**
   * Initialize search overlay
   */
  init() {
    this.createOverlay();
    this.bindEvents();
    this.loadPlaceholders();
  }

  /**
   * Get Project ID from dataLayer or use default
   */
  getProjectId() {
    if (
      typeof dataLayer !== 'undefined' &&
      dataLayer[0] &&
      dataLayer[0].shoptet
    ) {
      return dataLayer[0].shoptet.projectId;
    }
    return '312030'; // Default fallback
  }

  /**
   * Create and inject search overlay HTML
   */
  createOverlay() {
    const overlayHTML = `
            

        `;

    // Insert before #content-wrapper or at the beginning of body
    const contentWrapper = document.getElementById('content-wrapper');
    if (contentWrapper) {
      contentWrapper.insertAdjacentHTML('beforebegin', overlayHTML);
    } else {
      document.body.insertAdjacentHTML('afterbegin', overlayHTML);
    }
  }

  /**
   * Bind event listeners
   */
  bindEvents() {
    const formSearchForm = document.getElementById('formSearchForm');
    const overlay = document.getElementById('search-overlay');
    const input = document.getElementById('search-input');
    const close = document.getElementById('search-close');
    const submit = document.getElementById('search-submit');

    // Open search from header form
    if (formSearchForm) {
      console.log(
        '✅ SEARCH EVENTS - formSearchForm found, adding click listener'
      );
      formSearchForm.addEventListener('click', async e => {
        console.log('🖱️ SEARCH EVENTS - formSearchForm clicked!');
        e.preventDefault();
        await this.open();
      });
    } else {
      console.log('❌ SEARCH EVENTS - formSearchForm NOT FOUND!');
    }

    // Close search
    close?.addEventListener('click', () => this.close());
    overlay?.addEventListener('click', e => {
      if (e.target === overlay) this.close();
    });

    // Submit search
    submit?.addEventListener('click', () => {
      const query = this.getCurrentQuery().trim();
      if (query.length >= this.minSearchLength) {
        this.redirectToSearchResults(query);
      }
    });

    // Search input
    input?.addEventListener('input', e => this.handleSearch(e.target.value));
    input?.addEventListener('keydown', e => {
      if (e.key === 'Escape') {
        this.close();
      } else if (e.key === 'Enter') {
        e.preventDefault();
        const query = this.getCurrentQuery().trim();
        if (query.length >= this.minSearchLength) {
          this.redirectToSearchResults(query);
        }
      } else if (e.key === 'ArrowDown') {
        e.preventDefault();
        this.focusNextResult();
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        this.focusPreviousResult();
      }
    });

    // Keyboard navigation - global
    document.addEventListener('keydown', e => {
      if (e.key === '/' && !this.isOpen && document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
        e.preventDefault();
        this.open();
      }
      
      // Handle Tab key for focus trap when overlay is open
      if (this.isOpen && e.key === 'Tab') {
        this.handleTabKey(e);
      }
    });
    
    // Setup swipe to close (mobile)
    this.setupSwipeToClose();
  }

  /**
   * Setup swipe to close for mobile
   */
  setupSwipeToClose() {
    const overlay = document.getElementById('search-overlay');
    if (!overlay) return;
    
    let startY = 0;
    let currentY = 0;
    let isDragging = false;
    
    overlay.addEventListener('touchstart', (e) => {
      // Only if touch starts at the top of content
      if (e.target.closest('.search-overlay__header')) {
        startY = e.touches[0].clientY;
        isDragging = true;
      }
    }, { passive: true });
    
    overlay.addEventListener('touchmove', (e) => {
      if (!isDragging) return;
      
      currentY = e.touches[0].clientY;
      const diff = currentY - startY;
      
      // Only allow swipe down
      if (diff > 0) {
        const content = overlay.querySelector('.search-overlay__content');
        if (content) {
          content.style.transform = `translateX(-50%) translateY(${diff}px)`;
          content.style.opacity = Math.max(0.5, 1 - diff / 400);
        }
      }
    }, { passive: true });
    
    overlay.addEventListener('touchend', () => {
      if (!isDragging) return;
      
      const diff = currentY - startY;
      const content = overlay.querySelector('.search-overlay__content');
      
      if (diff > 100) {
        // Swipe down threshold - close overlay
        this.close();
      } else {
        // Return to position
        if (content) {
          content.style.transform = 'translateX(-50%)';
          content.style.opacity = '1';
        }
      }
      
      isDragging = false;
      startY = 0;
      currentY = 0;
    });
  }

  /**
   * Handle Tab key for focus trap
   */
  handleTabKey(e) {
    const overlay = document.getElementById('search-overlay');
    if (!overlay) return;

    const focusableElements = overlay.querySelectorAll(
      'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
    );
    
    const firstFocusable = focusableElements[0];
    const lastFocusable = focusableElements[focusableElements.length - 1];

    if (e.shiftKey) {
      // Shift + Tab
      if (document.activeElement === firstFocusable) {
        e.preventDefault();
        lastFocusable.focus();
      }
    } else {
      // Tab
      if (document.activeElement === lastFocusable) {
        e.preventDefault();
        firstFocusable.focus();
      }
    }
  }

  /**
   * Focus next result in list
   */
  focusNextResult() {
    const results = this.getFocusableResults();
    if (results.length === 0) return;

    this.currentFocusIndex = (this.currentFocusIndex + 1) % results.length;
    results[this.currentFocusIndex].focus();
  }

  /**
   * Focus previous result in list
   */
  focusPreviousResult() {
    const results = this.getFocusableResults();
    if (results.length === 0) return;

    this.currentFocusIndex = this.currentFocusIndex <= 0 ? results.length - 1 : this.currentFocusIndex - 1;
    results[this.currentFocusIndex].focus();
  }

  /**
   * Get all focusable results
   */
  getFocusableResults() {
    const resultsContainer = document.getElementById('search-content-results');
    if (!resultsContainer || resultsContainer.style.display === 'none') {
      return [];
    }
    
    return Array.from(resultsContainer.querySelectorAll('a[href], button:not([disabled])'));
  }

  /**
   * Load dynamic placeholders
   */
  async loadPlaceholders() {
    try {
      const response = await fetch(
        `${this.apiBaseUrl}/placeholders?projectId=${this.projectId}`,
        {
          headers: {
            'X-Tenant': this.projectId,
          },
        }
      );

      const data = await response.json();

      if (data.placeholders && data.placeholders.length > 0) {
        const input = document.getElementById('search-input');
        if (input) {
          input.placeholder = data.placeholders[0];
        }
      }
    } catch (error) {
      console.warn('Failed to load placeholders:', error);
    }
  }

  /**
   * Load recommendations for empty state
   */
  async loadRecommendations() {
    console.log('🚀 Starting to load recommendations...');
    console.log('API Base URL:', this.apiBaseUrl);
    console.log('Project ID:', this.projectId);

    try {
      const url = `${this.apiBaseUrl}/recommendations?projectId=${this.projectId}`;
      console.log('🔗 Fetching from URL:', url);

      const response = await fetch(url, {
        headers: {
          'X-Tenant': this.projectId,
        },
      });
      console.log('📡 Response status:', response.status);
      console.log('📡 Response ok:', response.ok);

      const data = await response.json();
      console.log('🔍 SEARCH RECOMMENDATIONS API - Data loaded:', data);

      return data;
    } catch (error) {
      console.error('❌ Failed to load recommendations:', error);
      return null;
    }
  }

  /**
   * Open search overlay
   */
  async open() {
    console.log('🚀 SEARCH OVERLAY - Opening search overlay...');
    
    // Store current focus to return later
    this.previousActiveElement = document.activeElement;
    
    // DataLayer: Search Started
    if (window.revelorDataLayer) {
      window.revelorDataLayer.searchStarted('overlay');
    }
    
    // Store start time for abandoned tracking
    this.searchStartTime = Date.now();
    this.lastSearchTerm = null;
    this.lastResultsCount = 0;
    
    this.isOpen = true;
    const overlay = document.getElementById('search-overlay');
    const input = document.getElementById('search-input');

    if (overlay) {
      overlay.classList.add('search-overlay--active');
      document.body.classList.add('search-overlay-open');
      console.log('🚀 SEARCH OVERLAY - Overlay activated');
    } else {
      console.log('❌ SEARCH OVERLAY - Overlay element not found!');
    }

    // Update aria-expanded on input
    if (input) {
      input.setAttribute('aria-expanded', 'true');
    }

    // Focus input after animation
    setTimeout(async () => {
      input?.focus();
      console.log('🚀 SEARCH OVERLAY - Input focused');

      // Show empty state with recommendations
      await this.showEmptyState();
    }, 100);
  }

  /**
   * Close search overlay
   */
  close() {
    // DataLayer: Search Abandoned (if there was a search term but no click)
    if (window.revelorDataLayer && this.lastSearchTerm && this.searchStartTime) {
      const timeSpent = ((Date.now() - this.searchStartTime) / 1000).toFixed(1);
      window.revelorDataLayer.searchAbandoned(
        this.lastSearchTerm,
        this.lastResultsCount,
        timeSpent,
        'closed_overlay'
      );
    }
    
    this.isOpen = false;
    const overlay = document.getElementById('search-overlay');
    const input = document.getElementById('search-input');

    if (overlay) {
      overlay.classList.remove('search-overlay--active');
      document.body.classList.remove('search-overlay-open');
    }

    // Update aria-expanded on input
    if (input) {
      input.setAttribute('aria-expanded', 'false');
    }

    // Return focus to previous element
    setTimeout(() => {
      if (this.previousActiveElement && this.previousActiveElement.focus) {
        this.previousActiveElement.focus();
      }
    }, 100);

    this.clearSearch();
  }

  /**
   * Clear search input and results
   */
  async clearSearch() {
    const input = document.getElementById('search-input');

    if (input) {
      input.value = '';
      input.focus();
    }

    await this.showEmptyState();
  }

  /**
   * Get current search query
   */
  getCurrentQuery() {
    const input = document.getElementById('search-input');
    return input ? input.value : '';
  }

  /**
   * Format price with dot as thousands separator and remove unnecessary decimals
   */
  formatPrice(price) {
    if (!price) return '';

    // Convert to number
    const numPrice = parseFloat(price);

    // Format as integer (bez desetinných míst) s tečkou jako oddělovač tisíců
    return Math.floor(numPrice).toLocaleString('cs-CZ', {
      minimumFractionDigits: 0,
      maximumFractionDigits: 0,
    });
  }

  /**
   * Redirect to search results page
   */
  redirectToSearchResults(query) {
    const url = `/vyhledavani/?string=${encodeURIComponent(query)}`;
    console.log('Redirecting to search results:', url);

    // Create temporary link and click it
    const tempLink = document.createElement('a');
    tempLink.href = url;
    tempLink.style.display = 'none';
    document.body.appendChild(tempLink);
    tempLink.click();
    document.body.removeChild(tempLink);
  }

  /**
   * Add product to cart (Shoptet API) - for Search Overlay
   * @param {Object} product - Product data
   * @param {HTMLElement} button - Button element
   */
  async addToCart(product, button) {
    const originalHTML = button.innerHTML;
    
    // Show loading state
    button.disabled = true;
    button.classList.add('loading');
    button.style.opacity = '0.5';
    
    try {
      // Check if Shoptet API is available
      if (typeof shoptet === 'undefined' || !shoptet.cartShared) {
        throw new Error('Shoptet API není k dispozici');
      }
      
      // Determine which method to use (podle Shoptet dokumentace)
      let addParams = {};
      
      if (product.priceId) {
        // Nejlepší varianta: priceId
        addParams = { priceId: product.priceId };
      } else if (product.kod || product.code) {
        // Fallback: product code
        addParams = { productCode: product.kod || product.code };
      } else if (product._id || product.id) {
        // Poslední varianta: product ID
        addParams = { productId: product._id || product.id };
      } else {
        throw new Error('Nelze identifikovat produkt');
      }
      
      // Add to cart (silent mode = true to prevent Shoptet's modal)
      await shoptet.cartShared.addToCart(addParams, true);
      
      // Success feedback
      button.classList.remove('loading');
      button.classList.add('success');
      button.style.opacity = '1';
      
      // TODO: Add toast notification if available
      
      // Tracking
      if (window.revelorDataLayer) {
        window.dataLayer.push({
          event: 'revelor_quick_add_to_cart',
          product_id: product._id || product.id,
          product_name: product.title,
          price: product.price_s_dph,
          source: 'search_overlay'
        });
      }
      
      // Reset button after 2s
      setTimeout(() => {
        button.disabled = false;
        button.classList.remove('success');
        button.style.opacity = '';
      }, 2000);
      
    } catch (error) {
      console.error('Add to Cart Error:', error);
      button.innerHTML = originalHTML;
      button.disabled = false;
      button.classList.remove('loading');
      button.style.opacity = '';
      // TODO: Add error notification
    }
  }

  /**
   * Handle search input
   */
  async handleSearch(query) {
    if (query.length < this.minSearchLength) {
      await this.showEmptyState();
      return;
    }

    // Only show loading if no results are currently displayed
    const resultsContainer = document.getElementById('search-content-results');
    const isShowingResults =
      resultsContainer && resultsContainer.style.display !== 'none';

    if (!isShowingResults) {
      this.showLoadingState();
    }

    // Perform search immediately
    this.performSearch(query);
  }

  /**
   * Perform search using API
   */
  async performSearch(query) {
    const searchUrl = `${this.apiBaseUrl}/suggestions?q=${encodeURIComponent(query)}&projectId=${this.projectId}&products_limit=6&categories_limit=6&articles_limit=6&brands_limit=0`;

    // DataLayer: Search Query Submitted
    if (window.revelorDataLayer) {
      window.revelorDataLayer.searchQuerySubmitted(query, 'overlay');
    }
    
    // Store for refinement tracking
    this.lastSearchTerm = query;
    
    // Check cache first
    const cacheKey = `${query}_${this.projectId}_suggestions`;
    const cached = this.getFromCache(cacheKey);
    if (cached) {
      console.log('📦 Using cached results for:', query);
      this.displayResults(cached, 0);
      return;
    }
    
    // Cancel previous request
    if (this.abortController) {
      this.abortController.abort();
    }
    this.abortController = new AbortController();
    
    const startTime = Date.now();

    try {
      const response = await this.fetchWithRetry(searchUrl, {
        signal: this.abortController.signal,
        headers: {
          'X-Tenant': this.projectId,
        },
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const data = await response.json();
      const responseTime = Date.now() - startTime;
      
      // Save to cache
      this.saveToCache(cacheKey, data);
      
      // Add to search history if has results
      const hasResults = data.products?.length > 0 || data.categories?.length > 0 || data.articles?.length > 0;
      if (hasResults) {
        this.addToSearchHistory(query);
      }
      
      this.displayResults(data, responseTime);
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('🚫 Request cancelled for:', query);
        return;
      }
      console.error('Search API Error:', error);
      
      // Try to use cached results as fallback
      const fallbackCached = this.getFromCache(cacheKey, true);
      if (fallbackCached) {
        console.log('📦 Using stale cache as fallback');
        this.displayResults(fallbackCached, 0);
        this.announceToScreenReader('Zobrazuji uložené výsledky (offline)');
        return;
      }
      
      this.showErrorState();
    }
  }

  /**
   * Save to cache
   */
  saveToCache(key, data) {
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    });
  }

  /**
   * Get from cache
   */
  getFromCache(key, ignoreExpiry = false) {
    const cached = this.cache.get(key);
    if (!cached) return null;
    
    // Check TTL (unless ignoreExpiry for fallback)
    if (!ignoreExpiry && Date.now() - cached.timestamp > this.cacheTTL) {
      this.cache.delete(key);
      return null;
    }
    
    return cached.data;
  }

  /**
   * Announce message to screen readers
   */
  announceToScreenReader(message) {
    const liveRegion = document.getElementById('search-live-region');
    if (liveRegion) {
      liveRegion.textContent = message;
      // Clear after announcement
      setTimeout(() => {
        liveRegion.textContent = '';
      }, 1000);
    }
  }

  /**
   * Display search results
   */
  displayResults(data, responseTime = 0) {
    this.hideAllStates();

    const hasResults =
      data.products?.length > 0 ||
      data.categories?.length > 0 ||
      data.articles?.length > 0;

    if (!hasResults) {
      this.showNoResultsState();
      this.announceToScreenReader('Žádné výsledky nebyly nalezeny');
      
      // DataLayer: Search No Results
      if (window.revelorDataLayer && this.lastSearchTerm) {
        window.revelorDataLayer.searchNoResults(this.lastSearchTerm);
      }
      
      this.lastResultsCount = 0;
      return;
    }

    this.showResultsState();

    // Count results
    const totalResults = 
      (data.products?.length || 0) + 
      (data.categories?.length || 0) + 
      (data.articles?.length || 0);
    
    // Store for abandoned tracking
    this.lastResultsCount = totalResults;
    
    // Announce results
    const resultTypes = [];
    if (data.products?.length) resultTypes.push(`${data.products.length} ${data.products.length === 1 ? 'produkt' : 'produkty'}`);
    if (data.categories?.length) resultTypes.push(`${data.categories.length} ${data.categories.length === 1 ? 'kategorie' : 'kategorie'}`);
    if (data.articles?.length) resultTypes.push(`${data.articles.length} ${data.articles.length === 1 ? 'článek' : 'články'}`);
    
    this.announceToScreenReader(`Nalezeno ${totalResults} výsledků: ${resultTypes.join(', ')}`);

    // DataLayer: Search Results Displayed
    if (window.revelorDataLayer && this.lastSearchTerm) {
      const resultsData = {
        products: data.products || [],
        categories: data.categories || [],
        articles: data.articles || [],
        total_products: totalResults
      };
      window.revelorDataLayer.searchResultsDisplayed(
        this.lastSearchTerm,
        resultsData,
        responseTime,
        1
      );
    }

    // Backend API: Track search event
    if (window.searchTracker && this.lastSearchTerm && this.lastSearchTerm.length >= 3) {
      window.searchTracker.trackSearch({
        query: this.lastSearchTerm,
        results_count: totalResults,
        search_type: 'live_search'
      });
    }

    // Display products
    if (data.products?.length > 0) {
      this.displayProducts(data.products);
    }

    // Display categories
    if (data.categories?.length > 0) {
      this.displayCategories(data.categories);
    }

    // Display articles
    if (data.articles?.length > 0) {
      this.displayArticles(data.articles);
    }

    // Reset focus index
    this.currentFocusIndex = -1;
  }

  /**
   * Display products
   */
  displayProducts(products) {
    const container = document.getElementById('search-products');
    const list = document.getElementById('search-products-list');
    const showAll = document.getElementById('search-products-show-all');

    if (!container || !list) return;

    container.style.display = 'block';
    list.innerHTML = '';

    products.forEach((product, index) => {
      const item = document.createElement('div');
      item.className = 'search-overlay__product';
      item.setAttribute('role', 'listitem');
      
      // Microdata attributes for AI agents
      item.setAttribute('itemscope', '');
      item.setAttribute('itemtype', 'https://schema.org/Product');
      
      const priceText = product.price_s_dph ? `${this.formatPrice(product.price_s_dph)} Kč` : 'Cena na dotaz';
      const availabilityText = product.skladem ? 'Skladem' : 'Není skladem';
      const availabilitySchema = product.skladem ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock';
      
      // Create JSON-LD structured data
      const jsonLd = {
        "@context": "https://schema.org/",
        "@type": "Product",
        "@id": product._id,
        "name": product.title,
        "description": product.description || product.title,
        "image": product.img,
        "url": product.url,
        "sku": product.kod,
        "offers": {
          "@type": "Offer",
          "price": product.price_s_dph || 0,
          "priceCurrency": "CZK",
          "availability": availabilitySchema,
          "url": product.url
        }
      };
      
      item.innerHTML = `
                
                
                
                
                ${product.skladem && !product.has_variants ? `
                  
                ` : ''}
            `;
      
      // Add click event for tracking
      const link = item.querySelector('.search-overlay__product-link');
      if (link) {
        link.addEventListener('click', () => {
          // DataLayer: Search Result Click
          if (window.revelorDataLayer && this.lastSearchTerm) {
            window.revelorDataLayer.searchResultClick(
              this.lastSearchTerm,
              'product',
              product,
              index + 1,
              this.lastResultsCount
            );
          }

          // Backend API: Track click event
          if (window.searchTracker && this.lastSearchTerm) {
            window.searchTracker.trackClick({
              clicked_result: product._id || product.id,
              product_id: product._id || product.id,
              result_type: 'product',
              result_title: product.title,
              result_url: product.url,
              result_position: index + 1,
              query: this.lastSearchTerm
            });
          }
        });
      }

      // Add click event for "Add to Cart" button
      const addBtn = item.querySelector('.search-overlay__product-add-btn');
      if (addBtn) {
        addBtn.addEventListener('click', async (e) => {
          e.preventDefault();
          e.stopPropagation();
          await this.addToCart(product, addBtn);
        });
      }

      list.appendChild(item);
    });

    if (showAll) {
      showAll.style.display = 'block';
      // Set up click handler for "Show all" link
      const showAllLink = showAll.querySelector(
        '.search-overlay__show-all-link'
      );
      if (showAllLink) {
        showAllLink.addEventListener('click', e => {
          e.preventDefault();
          e.stopPropagation();
          const url = `/vyhledavani/?string=${encodeURIComponent(this.getCurrentQuery())}`;
          console.log('Redirecting to:', url);

          // Create temporary link and click it
          const tempLink = document.createElement('a');
          tempLink.href = url;
          tempLink.style.display = 'none';
          document.body.appendChild(tempLink);
          tempLink.click();
          document.body.removeChild(tempLink);

          return false;
        });
      }
    }
  }

  /**
   * Display categories
   */
  displayCategories(categories) {
    const container = document.getElementById('search-categories');
    const list = document.getElementById('search-categories-list');
    const showAll = document.getElementById('search-categories-show-all');

    if (!container || !list) return;

    container.style.display = 'block';
    list.innerHTML = '';

    categories.forEach((category, index) => {
      const item = document.createElement('div');
      item.className = 'search-overlay__category';
      item.setAttribute('role', 'listitem');
      item.innerHTML = `
                
                    

${category.title}

`; // Add click tracking for category const link = item.querySelector('.search-overlay__category-link'); if (link && window.searchTracker && this.lastSearchTerm) { link.addEventListener('click', () => { window.searchTracker.trackClick({ clicked_result: category._id || category.id || category.url, result_type: 'category', result_title: category.title, result_url: category.url, result_position: index + 1, query: this.lastSearchTerm }); }); } list.appendChild(item); }); if (showAll) { showAll.style.display = 'block'; // Set up click handler for "Show all" link const showAllLink = showAll.querySelector( '.search-overlay__show-all-link' ); if (showAllLink) { showAllLink.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); const url = `/vyhledavani/?string=${encodeURIComponent(this.getCurrentQuery())}`; console.log('Redirecting to:', url); // Create temporary link and click it const tempLink = document.createElement('a'); tempLink.href = url; tempLink.style.display = 'none'; document.body.appendChild(tempLink); tempLink.click(); document.body.removeChild(tempLink); return false; }); } } } /** * Display articles */ displayArticles(articles) { const container = document.getElementById('search-articles'); const list = document.getElementById('search-articles-list'); const showAll = document.getElementById('search-articles-show-all'); if (!container || !list) return; container.style.display = 'block'; list.innerHTML = ''; articles.forEach((article, index) => { const item = document.createElement('div'); item.className = 'search-overlay__article'; item.setAttribute('role', 'listitem'); const description = article.description || ''; const ariaLabel = description ? `Článek ${article.title}: ${description}` : `Článek ${article.title}`; item.innerHTML = `

${article.title}

${description ? `

${description}

` : ''}
`; // Add click tracking for article const link = item.querySelector('.search-overlay__article-link'); if (link && window.searchTracker && this.lastSearchTerm) { link.addEventListener('click', () => { window.searchTracker.trackClick({ clicked_result: article._id || article.id || article.url, result_type: 'article', result_title: article.title, result_url: article.url, result_position: index + 1, query: this.lastSearchTerm }); }); } list.appendChild(item); }); if (showAll) { showAll.style.display = 'block'; // Set up click handler for "Show all" link const showAllLink = showAll.querySelector( '.search-overlay__show-all-link' ); if (showAllLink) { showAllLink.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); const url = `/vyhledavani/?string=${encodeURIComponent(this.getCurrentQuery())}`; console.log('Redirecting to:', url); // Create temporary link and click it const tempLink = document.createElement('a'); tempLink.href = url; tempLink.style.display = 'none'; document.body.appendChild(tempLink); tempLink.click(); document.body.removeChild(tempLink); return false; }); } } } /** * Display brands */ displayBrands(brands) { const container = document.getElementById('search-brands'); const list = document.getElementById('search-brands-list'); const showAll = document.getElementById('search-brands-show-all'); if (!container || !list) return; container.style.display = 'block'; list.innerHTML = ''; brands.forEach((brand, index) => { console.log('Brand data:', brand); // Debug log const item = document.createElement('div'); item.className = 'search-overlay__brand'; if (brand.url) { // Brand má URL - vytvoř proklik item.innerHTML = `

${brand.title}

${brand.description || ''}

`; // Add click tracking for brand (treat as category for now) const link = item.querySelector('.search-overlay__brand-link'); if (link && window.searchTracker && this.lastSearchTerm) { link.addEventListener('click', () => { window.searchTracker.trackClick({ clicked_result: brand._id || brand.id || brand.url, result_type: 'category', // Brands are tracked as categories result_title: brand.title, result_url: brand.url, result_position: index + 1, query: this.lastSearchTerm }); }); } } else { // Brand nemá URL - zobraz bez prokliku item.innerHTML = `

${brand.title}

${brand.description || ''}

`; } list.appendChild(item); }); if (showAll) { showAll.style.display = 'block'; // Set up click handler for "Show all" link const showAllLink = showAll.querySelector( '.search-overlay__show-all-link' ); if (showAllLink) { showAllLink.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); const url = `/vyhledavani/?string=${encodeURIComponent(this.getCurrentQuery())}`; console.log('Redirecting to:', url); // Create temporary link and click it const tempLink = document.createElement('a'); tempLink.href = url; tempLink.style.display = 'none'; document.body.appendChild(tempLink); tempLink.click(); document.body.removeChild(tempLink); return false; }); } } } /** * Show loading state */ showLoadingState() { this.hideAllStates(); const loading = document.getElementById('search-loading'); if (loading) loading.style.display = 'block'; } /** * Show empty state */ async showEmptyState() { console.log('📋 SEARCH EMPTY STATE - Starting showEmptyState()'); this.hideAllStates(); const empty = document.getElementById('search-empty'); if (empty) { empty.style.display = 'block'; console.log('📋 SEARCH EMPTY STATE - Empty element displayed'); // Show search history if available this.displaySearchHistory(); } else { console.log('❌ SEARCH EMPTY STATE - Empty element not found!'); } // Load recommendations for empty state console.log('📋 SEARCH EMPTY STATE - About to load recommendations...'); const recommendations